<html>
    <head>
        <title>扩展实体</title>
    </head>
    <body>
        <script>
            //  自定义应用程序模块：  扩展实体

                    /*
                        在某些情况下，你可能希望为依赖模块中定义的实体添加一些附加属性（和数据库字段）。
                        
                        本节将介绍一些不同的方法（与上一节不同的方式）来实现这一点。
                    */

                    /*
                        一、额外属性系统

                                额外属性是一种在实体上存储一些额外数据而不改变它的方法。

                                实体应该实现IHasExtraProperties接口来开启额外属性功能。

                                预构建模块中定义的所有聚合根实体都实现了IHasExtraProperties接口，因此你可以在这些对象上存储额外的属性。

                                eg:

                                    // 设置一个额外属性

                                            var user = await _identityUserRepository.GetAsync(userId);
                                            user.SetProperty("Title", "My custom title value!");
                                            await _identityUserRepository.UpdateAsync(user);

                                    // 获取一个额外属性

                                            var user = await _identityUserRepository.GetAsync(userId);
                                            return user.GetProperty<string>("Title");

                                这种方法非常易于使用并且开箱即用。不需要额外的代码。

                                你可以通过使用不同的属性名称（如此处的Title）同时存储多个属性。

                                额外的属性在EF Core的数据库中存储为单个JSON格式的字符串值。（对于MongoDB，它们存储为文档的单独字段。）

                                有关额外属性系统的更多信息，请参阅实体文档。 https://abp.io/docs/latest/framework/architecture/domain-driven-design/entities

                                备注：

                                    可以根据额外属性的值执行业务逻辑。你可以覆盖服务方法，然后获取或设置如上所示的值。
                    */

                    /*
                        二、EF Core 实体扩展

                                如上所述，实体的所有额外属性都作为单个JSON对象存储在数据库表中。
                                
                                这不是很自然，尤其是当你想如下做的时候：

                                    ①、为额外属性创建索引和外键。

                                    ②、使用额外属性（例如，按属性值搜索表）写入SQL或LINQ。

                                    ③、创建你自己的实体映射到同一个表，但将额外的属性定义为实体中的常规属性（有关详细信息，请参阅EF Core迁移文档）。

                                为了克服上述困难，"额外属性系统"针对 Entity Framework Core ，它允许你使用上面定义的相同的额外属性API,但将所需属性存储为数据库表中的单独字段。

                                假设要将SocialSecurityNumber添加到Identity Module的IdentityUser实体。你可以使用ObjectExtensionManager：

                                          ObjectExtensionManager.Instance
                                                                .MapEfCoreProperty<IdentityUser, string>(  // 配置到数据库表独立字段的映射
                                                                    "SocialSecurityNumber",
                                                                    (entityBuilder, propertyBuilder) =>
                                                                    {
                                                                        propertyBuilder.HasMaxLength(32);
                                                                    }  

                                        你提供了IdentityUser作为实体名称，string作为新属性的类型，SocialSecurityNumber作为属性名称（也是数据库表中的字段名称）。    

                                        你还需要提供一个使用EF Core Fluent API定义数据库映射属性的操作。

                                备注： 此代码部分必须在使用相关的DbContext之前执行。

                                      应用程序启动模板定义了一个名为YourProjectNameEfCoreEntityExtensionMappings的静态类。
                                      
                                      你可以在该类中定义你的扩展以确保它在适当的时间执行。否则，你应该自己处理。

                            
                                定义实体扩展后，你需要使用EF Core的标准Add-迁移和Update-Database命令来创建代码优先迁移类并更新数据库。

                                然后，你可以使用上一节中定义的相同额外属性系统来操作实体上的属性。
                    */

                    /*
                        三、创建到同一数据库表/集合的新实体映射

                                另一种方法可以是创建你自己的实体映射到相同的数据库表（或MongoDB数据库的集合）。
                    */

                    /*
                        四、使用自己的数据库表/集合创建新实体

                                将你的实体映射到依赖模块的现有表有一些缺点：

                                    ①、你处理EF Core的数据库迁移结构。虽然可能，但你应该格外注意迁移代码，尤其是当你想在实体之间添加关系时。

                                    ②、你的应用程序数据库和模块数据库将是相同的物理数据库。通常，如果需要，可以将模块数据库分开，但使用相同的表会限制它。


                                如果你想将你的实体与模块定义的实体松散耦合，你可以创建自己的数据库表/集合并将你的实体映射到你自己的数据库中自己的表。

                                在这种情况下，你需要处理同步问题，特别是如果你想复制相关实体的一些属性/字段。有几个解决方案：

                                    ①、如果你正在构建一个单体应用程序（或在同一进程中管理你的实体和相关模块实体），你可以使用本地事件总线来侦听更改。

                                    ②、如果你正在构建一个分布式系统，其中模块实体在与你的实体管理不同的进程/服务上进行管理（创建/更新/删除），
                                    
                                            那么你可以订阅分布式事件总线以获取更改事件。

                                处理事件后，你可以在自己的数据库中更新自己的实体。


                                1、使用本地事件总线

                                          本地事件总线系统是一种发布和订阅同一应用程序中发生的事件的方式
                                          
                                          假设你希望在IdentityUser实体更改（创建、更新或删除）时获得通知。
                                          
                                          你可以创建一个实现ILocalEventHandler<EntityChangedEventData<IdentityUser>>接口的类：

                                                public class MyLocalIdentityUserChangeEventHandler :
                                                    ILocalEventHandler<EntityChangedEventData<IdentityUser>>,
                                                    ITransientDependency
                                                {
                                                    public async Task HandleEventAsync(EntityChangedEventData<IdentityUser> eventData)
                                                    {
                                                        var userId = eventData.Entity.Id;
                                                        var userName = eventData.Entity.UserName; 
                                                        
                                                        //...
                                                    }
                                                }

                                          EntityChangedEventData<T>涵盖了给定实体的创建、更新和删除事件。如果需要，你可以单独订阅创建、更新和删除事件（在同一类或不同类中）。

                                          这段代码将在当前工作单元中执行，整个过程变成事务性的。
                                
                                2、使用分布式事件总线

                                          分布式事件总线系统是一种在一个应用程序中发布事件并在运行在相同或不同服务器上的相同或不同应用程序中接收事件的方式。

                                          假设你希望在Tenant实体（属于租户管理模块）创建时获得通知。
                                          
                                          在这种情况下，你可以订阅EntityCreatedEto<TenantEto>事件，如以下示例所示：
                                         
                                                public class MyDistributedEventHandler :
                                                    IDistributedEventHandler<EntityCreatedEto<TenantEto>>,
                                                    ITransientDependency
                                                {
                                                    public async Task HandleEventAsync(EntityCreatedEto<TenantEto> eventData)
                                                    {
                                                        var tenantId = eventData.Entity.Id;
                                                        var tenantName = eventData.Entity.Name;
                                                        //...your custom logic
                                                    }

                                                    //...
                                                }

                                          此处理程序仅在创建新租户时执行。所有预构建的ABP应用程序模块都为其实体定义了相应的ETO类型。因此，你可以在它们更改时轻松获得通知。


                    */
        </script>
    </body>
</html>